package com.nineclock.attendance.services.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.nineclock.attendance.dto.AttendGroupDTO;
import com.nineclock.attendance.dto.AttendPunchDTO;
import com.nineclock.attendance.dto.AttendPunchUserWholeDayDTO;
import com.nineclock.attendance.dto.AttendsDTO;
import com.nineclock.attendance.enums.AttendEnums;
import com.nineclock.attendance.mapper.AttendPunchMapper;
import com.nineclock.attendance.pojo.AttendPunch;
import com.nineclock.attendance.services.AttendGroupService;
import com.nineclock.attendance.services.AttendPunchService;
import com.nineclock.attendance.utils.PunchUtils;
import com.nineclock.common.enums.ResponseEnum;
import com.nineclock.common.exception.NcException;
import com.nineclock.common.threadlocal.CurrentUserHolder;
import com.nineclock.common.utils.BeanHelper;
import com.nineclock.common.utils.DateTimeUtil;
import com.nineclock.common.utils.IdWorker;
import com.nineclock.sign.dto.SignAllUserDTO;
import com.nineclock.sign.dto.SignDTO;
import com.nineclock.sign.pojo.SignInfo;
import com.nineclock.system.dto.SysCompanyUserDTO;
import com.nineclock.system.feign.OrganizationFeign;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.checkerframework.checker.units.qual.K;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 黑马程序员
 * @Company http://www.ithiema.com
 * @Version 1.0
 */
@Service
@Slf4j
public class AttendPunchServiceImpl implements AttendPunchService {

    @Autowired
    AttendGroupService attendGroupService;

    @Autowired
    AttendPunchMapper attendPunchMapper;

    @Autowired
    AttendPunchService attendPunchService;

    @Override
    public void punch(AttendPunchDTO attendPunchDTO) {
        if(attendPunchDTO == null){
            throw new NcException(ResponseEnum.INVALID_PARAM_ERROR);
        }
        //转换为pojo
        AttendPunch attendPunch = BeanHelper.copyProperties(attendPunchDTO, AttendPunch.class);
        //设置基础字段
        attendPunch.setCompanyUserId(CurrentUserHolder.get().getCompanyUserId());
        attendPunch.setCompanyId(CurrentUserHolder.get().getCompanyId());
        attendPunch.setPunchTime(new Date());
        attendPunch.setPunchDateStr(DateTimeUtil.dateToStr(new Date() , "yyyy-MM-dd"));//打卡日期（年月日）
        attendPunch.setCreateTime(new Date());

        //获取该员工对应的考勤组
        AttendGroupDTO attendGroup = attendGroupService.getAttendGroupByUserId();
        //设置是否工作日打卡
        boolean validateWorkDay = PunchUtils.validateWorkDay(attendGroup);
        if(!validateWorkDay){
            throw new NcException(ResponseEnum.PUNCH_INVALID_DAY);
        }
        //是否范围内有效打卡
        boolean validatePunchArea = PunchUtils.validatePunchArea(attendPunchDTO, attendGroup);
        if(!validatePunchArea){
            throw new NcException(ResponseEnum.PUNCH_INVALID_AREA);
        }

        //是否为有效打卡
        boolean validatePunch = this.validatePunch(attendPunch);
        //如果是有效打卡，设置打卡类型
        if(validatePunch){
            //设置上下文打卡类型
            PunchUtils.setPunchType(attendPunch, attendGroup);
        }


        attendPunchMapper.insert(attendPunch);
    }

    /**
     * 获取一整天的打卡情况
     * 1. 获取上午打卡情况
     * 2. 获取下午打卡情况
     * 3. 数据封装
     * @return
     */
    @Override
    public AttendPunchUserWholeDayDTO queryPunchRecord() {
        //获取当前登录员工id
        Long companyUserId = CurrentUserHolder.get().getCompanyUserId();
        //获取当前日期字符串
        String dateStr = DateTimeUtil.dateToStr(new Date(), "yyyy-MM-dd");

        //获取上午打卡情况
        LambdaQueryWrapper<AttendPunch> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(AttendPunch::getCompanyId, CurrentUserHolder.get().getCompanyId()); //当前企业
        wrapper.eq(AttendPunch::getCompanyUserId, companyUserId);  //当前员工
        wrapper.eq(AttendPunch::getPunchDateStr, dateStr); // 当天日期
        wrapper.eq(AttendPunch::getEffectiveValid, 1); //是否有效打卡
        wrapper.eq(AttendPunch::getPunchOnOffWork, 1); //上下午打卡

        AttendPunch attendPunchMorning = attendPunchMapper.selectOne(wrapper);


        //获取下午打卡情况
        LambdaQueryWrapper<AttendPunch> wrapper1 = new LambdaQueryWrapper<>();
        wrapper1.eq(AttendPunch::getCompanyId, CurrentUserHolder.get().getCompanyId()); //当前企业
        wrapper1.eq(AttendPunch::getCompanyUserId, companyUserId);  //当前员工
        wrapper1.eq(AttendPunch::getPunchDateStr, dateStr); // 当天日期
        wrapper1.eq(AttendPunch::getEffectiveValid, 1); //是否有效打卡
        wrapper1.eq(AttendPunch::getPunchOnOffWork, 2); //上下午打卡

        AttendPunch attendPunchAfterNoon = attendPunchMapper.selectOne(wrapper1);


        //封装数据
        AttendPunchUserWholeDayDTO attendPunchUserWholeDayDTO = new AttendPunchUserWholeDayDTO();

        attendPunchUserWholeDayDTO.setAttendPunchMorningResponse(BeanHelper.copyProperties(attendPunchMorning, AttendPunchDTO.class));
        attendPunchUserWholeDayDTO.setAttendPunchAfterNoonResponse(BeanHelper.copyProperties(attendPunchAfterNoon, AttendPunchDTO.class));

        return attendPunchUserWholeDayDTO;
    }

    @Autowired
    OrganizationFeign organizationFeign;

    /**
     * 当前企业内所有员工 指定时间范围内的考勤情况
     * 1. 获取当前企业内所有员工
     * 2. 获取日期区间内 所有的日期
     * 3. 再根据员工 查询 日期区间内每一天的考勤情况
     * @param startTime
     * @param endTime
     * @return
     */
    @Override
    public List<AttendPunchDTO> queryMembers(String startTime, String endTime) {
        //获取当前企业所有员工信息
        List<SysCompanyUserDTO> allCompanyUser = organizationFeign.queryAllCompanyUser().getData();
        // 获取 日期区间内时间列表
        List<Date> dateList = DateTimeUtil.getDatesBetweenTwoDate(startTime, endTime, "yyyy-MM-dd");
        //把日期列表 转换为 日期字符串列表
        List<String> dateStrList = dateList.stream().map(date -> {
            return DateTimeUtil.dateToStr(date, "yyyy-MM-dd");
        }).collect(Collectors.toList());

        //把员工列表 复制为 考勤列表(包含了基础字段)
        List<AttendPunchDTO> attendPunchDTOList = BeanHelper.copyWithCollection(allCompanyUser, AttendPunchDTO.class);
        //查询每一个员工考勤情况
        for (AttendPunchDTO attendPunchDTO : attendPunchDTOList) {
            LambdaQueryWrapper<AttendPunch> wrapper =  new LambdaQueryWrapper<>();
            wrapper.eq(AttendPunch::getCompanyId, CurrentUserHolder.get().getCompanyId());
            wrapper.eq(AttendPunch::getCompanyUserId, attendPunchDTO.getId());
            wrapper.eq(AttendPunch::getEffectiveValid, 1); //有效打卡
            wrapper.between(AttendPunch::getPunchDateStr, startTime, endTime); //获取 指定日期区间内的考勤情况
            //获取到了 某员工 日期区间内 有效打卡记录
            List<AttendPunch> attendPunchList = attendPunchMapper.selectList(wrapper);


            //对 打卡 记录 进行分组（按照 日期分组）
            Map<String, List<AttendPunch>> listMap = attendPunchList.stream().collect(Collectors.groupingBy(AttendPunch::getPunchDateStr));

            //存储员工每天的打卡情况
            List<AttendsDTO> attendsDTOS = new ArrayList<>();

            //循环日期列表
            for (String dateStr : dateStrList) {
                AttendsDTO attendsDTO = new AttendsDTO();
                //获取某一天的考勤情况
                List<AttendPunch> attendPunches = listMap.get(dateStr);
                attendsDTO.setAttendDate(dateStr);
                String attendStatus = this.handleAttendStatus(attendPunches);
                attendsDTO.setAttendStatus(attendStatus);
                //添加到集合中
                attendsDTOS.add(attendsDTO);
            }

            attendPunchDTO.setAttendsList(attendsDTOS);

        }

        return attendPunchDTOList;
    }

    @Override
    public void export(String startTime, String endTime) throws IOException {
        //1. 获取时间段 内的考勤情况
        List<AttendPunchDTO> attendPunchDTOList = this.queryMembers(startTime, endTime);


        //2. 获取响应对象，并设置 响应对象
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletResponse response = servletRequestAttributes.getResponse();
        response.setHeader("Content-disposition", "attachment;filename=Attendance.xlsx");
        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setCharacterEncoding("utf-8");


        // *************easyExcel **********

        WriteCellStyle cellStyle = new WriteCellStyle();
        //表头和内容都是水平居中
        cellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);

        //设置字体
        WriteFont font = new WriteFont();
        font.setFontName("宋体");
        font.setFontHeightInPoints((short)12);
        cellStyle.setWriteFont(font);

        HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(cellStyle,cellStyle);

        EasyExcel.write(response.getOutputStream())
                .head(handlerHead(attendPunchDTOList))
                .registerWriteHandler(styleStrategy)
                .registerWriteHandler(new MyColumnStyle())
                .sheet("测试数据")
                .doWrite(handlerData(attendPunchDTOList));

        // *************easyExcel **********
    }



    //操作数据
    private List<List<String>> handlerData(List<AttendPunchDTO> attendPunchDTOList) {
        List<List<String>> data = new ArrayList<>();
        //循环 每一个 员工
        for (AttendPunchDTO attendPunchDTO : attendPunchDTOList) {
            List<String> list = new ArrayList<>();
            list.add(attendPunchDTO.getUserName());
            list.add(attendPunchDTO.getWorkNumber());
            list.add(attendPunchDTO.getDepartmentName());
            list.add(attendPunchDTO.getPost());
            //取出 员工每天的考勤状态
            List<AttendsDTO> attendsList = attendPunchDTO.getAttendsList();
            for (AttendsDTO attendsDTO : attendsList) {
                list.add(attendsDTO.getAttendStatus());
            }


            //添加到data中
            data.add(list);

        }

        return data;
    }

    /**
     * 操作表头
     * @return
     */
    private List<List<String>> handlerHead(List<AttendPunchDTO> attendPunchDTOList) {
        List<List<String>> head = new ArrayList<>();
        head.add(Arrays.asList("姓名"));
        head.add(Arrays.asList("工号"));
        head.add(Arrays.asList("部门"));
        head.add(Arrays.asList("职位"));
        //获取集合中的第一个对象
        //获取了第一个员工的每天打卡情况 对应 日期
        AttendPunchDTO attendPunchDTO = attendPunchDTOList.get(0);
        List<AttendsDTO> attendsList = attendPunchDTO.getAttendsList();
        for (AttendsDTO attendsDTO : attendsList) {
            head.add(Arrays.asList(attendsDTO.getAttendDate()));
        }
        return head;
    }

    private String handleAttendStatus(List<AttendPunch> punches) {
        String amAttendStatus = "缺卡";//上午 - 考勤
        String pmAttendStatus = "缺卡";//下午 - 考勤
        if(CollectionUtil.isNotEmpty(punches)){
            for (AttendPunch punch : punches) {
                if(punch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_MORNING.value()){
                    amAttendStatus = this.getPushDesc(punch.getMorningPunchType());
                }else if (punch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_AFTERNOON.value()){
                    pmAttendStatus = this.getPushDesc(punch.getAfternoonPunchType());
                }
            }
        }
        return amAttendStatus+"/"+pmAttendStatus;
    }

    private String getPushDesc(Integer morningPunchType) {
        String desc = "";
        switch (morningPunchType){
            case 1:
                desc = AttendEnums.PUNCH_TYPE_OK.toString();
                break;
            case 2:
                desc = AttendEnums.PUNCH_TYPE_LATE.toString();
                break;
            case 3:
                desc = AttendEnums.PUNCH_TYPE_EARLY.toString();
                break;
            case 4:
                desc = AttendEnums.PUNCH_TYPE_STAYALAY.toString();
                break;
            case 5:
                desc = AttendEnums.PUNCH_TYPE_NOT_WORK_DAY.toString();
                break;
            default:
                break;
        }
        return desc;
    }

    /**
     * 1. 查询打卡记录，如果没有查询到，无论何时打卡都是有效的
     * 2. 查询到了
     *  如果是上午打卡， 则无效
     *  如果是下午打卡， 以最后一次打卡记录为准, 之前打卡记录无效
     * @param attendPunch
     * @return
     */
    private boolean validatePunch(AttendPunch attendPunch) {
        LambdaQueryWrapper<AttendPunch> punchQw = new LambdaQueryWrapper<>();

        punchQw.eq(AttendPunch::getPunchOnOffWork, attendPunch.getPunchOnOffWork());
        punchQw.eq(AttendPunch::getCompanyId, CurrentUserHolder.get().getCompanyId());
        punchQw.eq(AttendPunch::getCompanyUserId, CurrentUserHolder.get().getCompanyUserId());
        punchQw.eq(AttendPunch::getPunchDateStr , attendPunch.getPunchDateStr());
        //查询 当天 打卡记录
        Integer count = attendPunchMapper.selectCount(punchQw);

        if(count == 0){
            //如果第一次打卡，都是有效打卡
            attendPunch.setEffectiveValid(true);
        }else{
            //如果是上午打卡，只有第一次有效，其他无效
            if(attendPunch.getPunchOnOffWork() == 1){
                attendPunch.setEffectiveValid(false);
            }else{
                //如果是下午打卡，最后一次， 有效打卡
                attendPunch.setEffectiveValid(true);
                //之前的打卡设置为无效打卡
                AttendPunch updateAttendPunch = new AttendPunch();
                updateAttendPunch.setEffectiveValid(false);
                attendPunchMapper.update(updateAttendPunch, punchQw);

            }


        }

        return attendPunch.getEffectiveValid();
    }

    class MyColumnStyle extends AbstractColumnWidthStyleStrategy {

        @Override
        protected void setColumnWidth(WriteSheetHolder writeSheetHolder,
                                      List<CellData> cellDataList,
                                      Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
            writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), 4500);
        }
    }
}
